package be.nepherte.movmanager.client.gui.components;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;

/**
 * SortableTable is a type of FlexTable which allows sorting
 * on its column. Sorting is done completely on the client side.
 * No server side call is made in this table.
 * 
 * Current implementation of Sortable Table needs <code>Comparable</code>
 * Object in its column values to be able to sort them correctly.
 * 
 * The objects being set in the column values must implement the interface
 * <code>Comparable</code> and implement methods: <code>compareTo()</code> and 
 * <code>toString()</code>
 * <p>
 * This file is part of MovManager.
 * </p>
 * <p>
 * MovManager is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * <p>
 * MovManager is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManager. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses</a>.
 * </p>
 * 
 * @author 	Bart Verhoeven, Parvinder Thapar
 * @since 	20/05/2009
 * @version 0.1
 */
public class SortableTable extends FlexTable implements Sortable, ClickHandler {
	/**Holds the current column being sorted.*/
	private int sortColIndex = -1;
	/**Holds the current direction of sort: ascending or descending.*/
	private int sortDirection =	-1;
	/**The default image to show ascending order arrow.*/
	private final String sortAscImage = GWT.getModuleBaseURL() + "images/asc.gif";
	/**The default image to show descending order arrow.*/
	private final String sortDescImage = GWT.getModuleBaseURL() + "images/desc.gif";
	/**The default image to show the blank image. This is needed to paint the columns other than the one which is being sorted. should be same length and width as the asc/ desc images.*/
	private final String blankImage = GWT.getModuleBaseURL() + "images/blank.gif";
	/**Holds the data rows of the table.*/
	private List<RowData> tableRows = new ArrayList<RowData>();
	/**Holds the data for the column headers.*/
	private List<String> tableHeader = new ArrayList<String>();
	
	/**
	 * Default Constructor. Call the super class constructor
	 * and add a click handler object.
	 */
	public SortableTable(){
		super();
		this.addClickHandler(this);
	}

	/**
	 * Add a column header to the table.
	 *  
	 * @param	name
	 * 			the name you want to have as header of the column	
	 * @param	index
	 * 			the index of the column
	 */
	public void addColumnHeader(String name, int index){
		tableHeader.add(index, name);
		this.renderTableHeader(name, index);
	}
	
	/**
	 * Set the value in the specified cell. It will ignore every value you try to set in row 0, which is used as table header.
	 *  
	 * @param	rowIndex
	 * 			the index of the row you want to add the value to
	 * @param	colIndex
	 * 			the index of the column you want to add the value to
	 * @param	value
	 * 			the value you want to set
	 */
	public void setValue(int rowIndex, int colIndex, Comparable<?> value){
		// The rowIndex should begin with 1 as rowIndex 0 is for the Header
		if (rowIndex == 0)
			return;
		
		if((rowIndex-1) >= this.tableRows.size() || null == tableRows.get(rowIndex-1))
			tableRows.add(rowIndex-1, new RowData());
		
		RowData rowData = this.tableRows.get(rowIndex-1); 
		rowData.addColumnValue(colIndex, value);
		this.setHTML(rowIndex, colIndex, "" + value.toString()+ "");
	}
	
	/**
	 * Implementation of Sortable Interface. This method describes how to sort
	 * the specified column. It checks the current sort direction and flips it.
	 * 
	 * @param	column
	 *          index the index of the column you want to sort
	 */
	@SuppressWarnings("unchecked")
	public void sort(int columnIndex){
		Collections.sort(this.tableRows);
		
		// if different column clicked set ascending else flip sorting direction
		if (this.sortColIndex != columnIndex)
			this.sortDirection = SORT_ASC;
		else 
			this.sortDirection = (this.sortDirection == SORT_ASC)? SORT_DESC:SORT_ASC;
		
		this.sortColIndex = columnIndex;
	}
	
	/**
	 * Specify what happens upon clicking a column header: flip the sorting direction and sort the column.
	 */
	public void onClick(ClickEvent event) {
		Cell clickedCell = getCellForEvent(event);
		int col = clickedCell.getCellIndex();
		
		if (clickedCell.getRowIndex() != 0)
			return;
		
		this.setSortColIndex(col);
		this.sort(col);
		this.drawTable();
	}
	
	/**
	 * Render the whole table.
	 */
	protected void drawTable() {
		this.displayTableHeader();
		this.displayTableBody();
	}
	
	/**
	 * Render the table header.
	 */
	private void displayTableHeader() {
		int colIndex=0;
		
		for (String colHeader : tableHeader)
			this.renderTableHeader(colHeader, colIndex++);
	}
	
	/**
	 * Render the body or the remaining rows of the table except the header. 
	 * Check the sort direction and display the rows accordingly.
	 */
	private void displayTableBody(){
		if (this.sortDirection == SORT_ASC || this.sortDirection == -1) {
			// Ascending order and Default Display
			for (int rowIndex=0; rowIndex<tableRows.size(); rowIndex++) {
				RowData columns = tableRows.get(rowIndex);
				
				for (int colIndex=0; colIndex<columns.getColumnValues().size(); colIndex++) {
					Object value = columns.getColumnValue(colIndex);
					
					if (null != value)
						this.setHTML(rowIndex+1, colIndex, value.toString());
				}
			}
		} else {
			// Descending Order Display
			for (int rowIndex=tableRows.size()-1, rowNum = 1; rowIndex>=0; rowIndex--, rowNum++) {
				RowData columns = tableRows.get(rowIndex);
				
				for (int colIndex=0; colIndex<columns.getColumnValues().size(); colIndex++) {
					Object value = columns.getColumnValue(colIndex);
					
					if (null != value)
						this.setHTML(rowNum, colIndex, value.toString());
				}
			}
		}
	}
	
	/**
	 * Set the index of the column to sort.
	 * 
	 * @param	sortIndex
	 * 			the index of the column to sort
	 */
	private void setSortColIndex(int sortIndex) {
		for (int rowIndex=0; rowIndex<tableRows.size(); rowIndex++) {
			RowData row = tableRows.get(rowIndex);
			row.setSortColIndex(sortIndex);
		}
	}
	
	/**
	 * Render a particular column in the table header.
	 * 
	 * @param 	name
	 * 			the name you want to set for this column
	 * @param	index
	 * 			the index of the column you want to render
	 */
	private void renderTableHeader(String name, int index) {
		StringBuffer headerText = new StringBuffer();
		headerText.append(name);
		headerText.append("&nbsp;<img border='0' src=");
		
		if (this.sortColIndex == index) {
			if (this.sortDirection == SORT_ASC)
				headerText.append("'" + this.sortAscImage + "' alt='Ascending' ");	
			else
				headerText.append("'" + this.sortDescImage + "' alt='Descending' ");
		} else {
			headerText.append("'" + this.blankImage + "'");
		}
		
		headerText.append("/>");
		this.setHTML(0, index, headerText.toString());
	}
}